package model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;


import exception.DrawException;

import toolkit.Counter;

public class Combination {
	
	private CombinationRank rank = null;
	
	private ArrayList<Card> cards = new ArrayList<Card>(5);
	
	private final CardContainer cardContainer = new CardContainer();
	
	private Participant participant;
	
	public Combination()
	{
		
	}
	
	public void setParticipant(Participant participant)
	{
		this.participant = participant;
	}
	
	public Participant getParticipant()
	{
		return participant;
	}
	
	public void addCards(CardContainer cardsContainer)
	{
		addCards(cardsContainer.getCards());
	}
	
	public void addCards(Collection<Card> cards)
	{
		cardContainer.addCards(cards);
	}
	
	public void calculateBestCombination()
	{
		EnumMap<CardSuit, EnumMap<CardRank, Counter>> map = cardContainer.countCardsGrouped();

		Card straightFlushCard = null;
		
		Card flushCard = null;
		
		for (CardSuit suit: CardSuit.values())
		{
			EnumMap<CardRank, Counter> suitMap = map.get(suit);
			
			int count = 0;
			
			int straight = 0;
			
			for (int i = CardRank.MAX_INTEGER_VALUE; i >= CardRank.MIN_INTEGER_VALUE; i--)
			{
				CardRank cardRank = CardRank.valueOf(i);
				
				if (suitMap.get(cardRank).count() > 0)
				{
					if (i > CardRank.MIN_INTEGER_VALUE)
					{
						count++;
					}
					
					straight++;
				}
				else
				{
					straight = 0;
				}

				if (straight == 5)
				{
					// straight flush
					if (straightFlushCard == null || (straightFlushCard.getSuit() != suit && cardRank.toInteger() > straightFlushCard.getRank().toInteger()))
					{
						straightFlushCard = Card.valueOf(cardRank, suit);
					}
					
					break;
				}

				if (count == 5 && straightFlushCard == null)
				{
					// flush
					if (flushCard == null || (flushCard.getSuit() != suit && cardRank.toInteger() > flushCard.getRank().toInteger()))
					{
						flushCard = Card.valueOf(cardRank, suit);
					}
				}
			}
		}
		
		int highCardsCount = 0;
		
		ArrayList<Card> highCards = new ArrayList<Card>();
		
		ArrayList<ArrayList<Card>> pairList = new ArrayList<ArrayList<Card>>();

		ArrayList<ArrayList<Card>> threeKindList = new ArrayList<ArrayList<Card>>();

		CardRank fourKindRank = null;
		
		int straightCount = 0;

		ArrayList<Card> straightCards = new ArrayList<Card>();

		for (int i = CardRank.MAX_INTEGER_VALUE; i >= CardRank.MIN_INTEGER_VALUE; i--)
		{
			CardRank cardRank = CardRank.valueOf(i);
			
			int count = 0;
			
			ArrayList<Card> rankCards = new ArrayList<Card>();
			
			for (CardSuit suit: CardSuit.values())
			{
				if (map.get(suit).get(cardRank).count() > 0)
				{
					Card card = Card.valueOf(cardRank, suit);
					
					count++;
					
					rankCards.add(card);
					
					if (highCardsCount < 5)
					{
						highCardsCount++;
						
						highCards.add(card);
					}
				}
			}
			
			if (straightCount < 5)
			{
				if (count > 0)
				{
					straightCount++;
					
					straightCards.add(rankCards.get(0));
				}
				else
				{
					straightCount = 0;
					
					straightCards = new ArrayList<Card>();
				}
			}
			
			if (i > CardRank.MIN_INTEGER_VALUE)
			{
				if (count == 2)
				{
					// pair
					pairList.add(rankCards);
				}
				else if (count == 3)
				{
					// three of a kind
					threeKindList.add(rankCards);
				}
				else if (count == 4)
				{
					// four of a kind
					fourKindRank = cardRank;
				}
			}
		}
		
		if (straightFlushCard != null)
		{
			rank = CombinationRank.STRAIGHT_FLUSH;
			
			CardSuit suit = straightFlushCard.getSuit();
			
			int offset = straightFlushCard.getRank().toInteger();
			
			int limit = offset + 5;
			
			for (int i = limit - 1; i >= offset; i--)
			{
				cards.add(Card.valueOf(CardRank.valueOf(i), suit));
			}
		}
		else if (fourKindRank != null)
		{
			rank = CombinationRank.FOUR_OF_A_KIND;
			
			for (CardSuit suit: CardSuit.values())
			{
				cards.add(Card.valueOf(fourKindRank, suit));
			}
		}
		else if (threeKindList.size() >= 1 && (pairList.size() >= 1 || threeKindList.size() >= 2))
		{
			rank = CombinationRank.FULL_HOUSE;
			
			cards = threeKindList.get(0);
			
			boolean pair = pairList.size() >= 1;
			
			boolean threeKind = threeKindList.size() >= 2;
			
			ArrayList<Card> pairCards = null;
			
			if (pair)
			{
				pairCards = pairList.get(0);
			}
			
			if (threeKind)
			{
				ArrayList<Card> lesserThreeKindCards = threeKindList.get(1);
				
				if (pair)
				{
					if (lesserThreeKindCards.get(0).getRank().toInteger() > pairCards.get(0).getRank().toInteger())
					{
						pairCards = lesserThreeKindCards;
					}
				}
				else
				{
					pairCards = lesserThreeKindCards;
				}
			}
			
			cards.addAll(pairCards.subList(0, 2));
		}
		else if (flushCard != null)
		{
			rank = CombinationRank.FLUSH;
			
			CardSuit suit = flushCard.getSuit();
			
			EnumMap<CardRank, Counter> suitMap = map.get(suit);
			
			int count = 0;
			
			for (CardRank cardRank: CardRank.values())
			{
				if (suitMap.get(cardRank).count() > 0)
				{
					cards.add(Card.valueOf(cardRank, suit));
					
					if (++count == 5)
					{
						break;
					}
				}
			}
			
			Collections.reverse(cards);
		}
		else if (straightCards.size() == 5)
		{
			rank = CombinationRank.STRAIGHT;
			
			cards = straightCards;
		}
		else if (threeKindList.size() >= 1)
		{
			rank = CombinationRank.THREE_OF_A_KIND;
			
			cards = threeKindList.get(0);
		}
		else if (pairList.size() >= 2)
		{
			rank = CombinationRank.TWO_PAIR;
			
			cards.addAll(pairList.get(0));
			cards.addAll(pairList.get(1));
		}
		else if (pairList.size() == 1)
		{
			rank = CombinationRank.ONE_PAIR;
			
			cards.addAll(pairList.get(0));
		}
		else
		{
			rank = CombinationRank.HIGH_CARD;
			
			cards = highCards;
		}
		
		int fill = 5 - cards.size(); 
		
		if (fill > 0)
		{
			for (int index = 0; fill > 0; index++)
			{
				Card card;
				
				try
				{
					card = highCards.get(index);
				}
				catch (IndexOutOfBoundsException e)
				{
					break;
				}
				
				if (!cards.contains(card))
				{
					cards.add(card);
					
					fill--;
				}
			}
			
			//cards.addAll(highCards.subList(0, fill));
		}
	}
	
	public CombinationRank getRank()
	{
		return rank;
	}
	
	public ArrayList<Card> getCards()
	{
		return cards;
	}
	
	public CombinationCompareResult compare(Combination other)
	{
		if (other == null)
		{
			return CombinationCompareResult.WIN;
		}
		
		CombinationRank otherRank = other.getRank();
		
		if (rank == otherRank)
		{
			boolean straight = (rank == CombinationRank.STRAIGHT || rank == CombinationRank.STRAIGHT_FLUSH);
			
			ArrayList<Card> otherCards = other.getCards();
			
			for (int i = 0; i < 5; i++)
			{
				CardRank cardRank = cards.get(i).getRank();
				
				int cardValue = cardRank.toInteger();
				
				if (straight && i == 4 && cardRank == CardRank.ACE && cards.get(0).getRank() == CardRank.FIVE)
				{
					cardValue = 1;
				}
				
				CardRank otherCardRank = otherCards.get(i).getRank();
				
				int otherCardValue = otherCardRank.toInteger();
				
				if (straight && i == 4 && otherCardRank == CardRank.ACE && otherCards.get(0).getRank() == CardRank.FIVE)
				{
					otherCardValue = 1;
				}
				
				if (cardValue != otherCardValue)
				{
					return (cardValue > otherCardValue
						? CombinationCompareResult.WIN
						: CombinationCompareResult.LOSS
					);
				}
			}
		}
		else
		{
			return (rank.toInteger() > otherRank.toInteger()
				? CombinationCompareResult.WIN
				: CombinationCompareResult.LOSS
			);
		}
		
		return CombinationCompareResult.TIE;
	}
	
	public String toString()
	{
		if (rank == null)
		{
			return "Not calculated yet";
		}
		else
		{
			String combination = rank + "\n\n";
			
			for (Card card: cards)
			{
				combination += card + "\n";
			}
			
			return combination;
		}
	}
}
